package com.boat.starter.web.filter;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.lang3.StringUtils;
import org.springframework.web.filter.OncePerRequestFilter;

import com.boat.framework.base.api.Response;
import com.boat.framework.base.exception.ErrorCodeUtils;
import com.boat.framework.common.servlet.RequestWrapper;
import com.boat.framework.common.servlet.ResponseWrapper;
import com.boat.framework.common.util.JsonUtils;
import com.boat.framework.common.util.ServletUtils;
import com.boat.starter.web.excepton.GlobalExceptionHandler;

import cn.hutool.core.collection.ListUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import lombok.extern.slf4j.Slf4j;

/**
 * 日志打印拦截器
 * 
 * @author 周江
 *
 */
@Slf4j
public class LoggingFilter extends OncePerRequestFilter {
	/**
	 * 处于某种原因，如参数过大等。不需要框架打印log。业务代码自己打印
	 */
	public static List<String> IGNORE_LOG_URIS = new ArrayList<>();
	/**
	 * 需要忽视的静态资源mimeType
	 */
	public static List<String> IGNORE_STATIC_MIMETYPES = ListUtil.toList("text/css", "text/html","application/javascript","image/","octet-stream");

	private static final int DEFAULT_MAX_LENGTH = 200;
	private static final String REQ_LOG_TMPL = "收到请求: {uri}, contentType: {contentType}, 参数: {payload}";
	private static final String RESP_LOG_TMPL = "返回结果: {uri}, 结果: {payload}, 耗时: {times}";

	@Override
	protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
			throws ServletException, IOException {
		//为了能多次获取响应结果，包装请求和响应
		request = new RequestWrapper(request);
		response = new ResponseWrapper(response);
		String uri = request.getRequestURI();
		long startTime = System.currentTimeMillis();

		if (!isIgnoreUri(request)) {
			log(request);
		}

		try {
			filterChain.doFilter(request, response);
		} catch (Exception ex) {
			// 拦截器出错或404时，异常会被此代码捕获
			Throwable cause = ex.getCause()!=null?ex.getCause():ex;
			Response<Void> resp = GlobalExceptionHandler.convExceptionResponse(cause);
			if (ErrorCodeUtils.isNoPrintStack(resp.getCode())) {
				// 为避免日志太多，业务异常，不打印堆栈信息
				log.warn("系统出错，可能是请求的路径不存在或内部拦截器出错导致。错误消息：" + cause.getMessage()+"，uri："+uri);
			} else {
				log.warn("系统出错，可能是请求的路径不存在或内部拦截器出错导致。错误消息：" + cause.getMessage()+"，uri："+uri, cause);
			}
			ServletUtils.renderString(response, JsonUtils.toJson(resp));
		}

		if (!isIgnoreUri(request)) {
			log((ResponseWrapper) response, uri, startTime);
		}
	}

	private void log(final HttpServletRequest request) {
		String uri = request.getRequestURI();
		if (request.getQueryString() != null) {
			uri += "?" + request.getQueryString();
		}
		String payload = "";
		if (request instanceof RequestWrapper && !isMultipart(request) && !isBinaryContent(request)
				&& !isStream(request)) {
			RequestWrapper requestWrapper = (RequestWrapper) request;
			payload = requestWrapper.getRequestBodyString(request);
			// 如果是json格式，格式化一下，不然有很多空格不好看
			if (JSONUtil.isTypeJSON(payload)) {
				payload = JSONUtil.toJsonStr(JSONUtil.parse(payload));
			}
			payload = StrUtil.maxLength(payload, DEFAULT_MAX_LENGTH);
		}

		Map<String, Object> params = new HashMap<>(10);
		params.put("method", request.getMethod());
		params.put("contentType", request.getContentType());
		params.put("uri", StrUtil.maxLength(uri, DEFAULT_MAX_LENGTH));
		params.put("payload", StrUtil.maxLength(payload, DEFAULT_MAX_LENGTH));
		
		log.info(StrUtil.format(REQ_LOG_TMPL, params, false));
	}

	private void log(final ResponseWrapper response, String uri, long startTime) {
		String payload = "";
		try {
			payload = new String(response.toByteArray(), "utf-8");
			payload = StrUtil.maxLength(payload, DEFAULT_MAX_LENGTH);
		} catch (UnsupportedEncodingException ex) {
			log.warn("解析响应体失败：" + ex.getMessage(), ex);
		}
		long times = System.currentTimeMillis() - startTime;
		Map<String, Object> params = Map.of("uri", uri, "payload", payload, "times", times);
		log.info(StrUtil.format(RESP_LOG_TMPL, params, false));
	}

	private boolean isBinaryContent(final HttpServletRequest request) {
		if (request.getContentType() == null) {
			return false;
		}
		return request.getContentType().startsWith("image") || request.getContentType().startsWith("video")
				|| request.getContentType().startsWith("audio") || request.getContentType().startsWith("stream");
	}

	private boolean isMultipart(final HttpServletRequest request) {
		return request.getContentType() != null && request.getContentType().startsWith("multipart");
	}

	private boolean isStream(final HttpServletRequest request) {
		return request.getContentType() != null && request.getContentType().contains("stream");
	}

	private boolean isIgnoreUri(HttpServletRequest request) {
		String uri = request.getRequestURI();
		if(IGNORE_LOG_URIS.contains(uri)) {
			return true;
		}
		String mimeType = request.getServletContext().getMimeType(uri);
		if(StringUtils.containsAnyIgnoreCase(mimeType, IGNORE_STATIC_MIMETYPES.toArray(new String[0]))) {
			return true;
		}
		return false;
	}
}